#
# subroutines
#
# Common subroutines for the Dragora GNU/Linux-Libre website scripts
#
#
# Copyright (C) 2021, 2022 Michael Siegel
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


## Error handling

_abort() {
  ## Run _cleanup if "$TMP_DIR" exists and exit the script with a return code
  ## indicating an error

  _cleanup
  exit 1
}

_cleanup() {
  ## Remove any temporary files

  [[ -d "$TMP_DIR" ]] && rm -rf -- "${TMP_DIR:?}"/*
}

_perr() {
  ## Print a message to stderr

  printf '%s\n' "$PROGNAME: $1" >&2
  # Don't print PROGNAME for error messages in interactive mode
}

## Environment checks

_command_available() {
  ## Tell whether the given external commands are available

  local cmd=
  local cmd_path=
  local err=0

  while [[ "$#" -gt 0 ]]
  do
    cmd="$1"
    cmd_path="$(command -v -- "$cmd")"

    if [[ -z "$cmd_path" ]]
    then
      _perr "command not found -- '$cmd'"
      err=1
    elif [[ ! -x "$cmd_path" ]]
    then
      _perr "command not executable -- '$cmd_path'"
      err=1
    fi
    shift
  done

  [[ "$err" -eq 1 ]] && return 1
  return 0
}

_command_is_gnu() {
  ## Tell whether the given external commands are from GNU

  local cmd=
  local err=0
  local gnu_version=

  while [[ "$#" -gt 0 ]]
  do
    cmd="$1"
    case "$cmd" in
      find)
        gnu_version='^find (GNU findutils)'
      ;;
      sed)
        gnu_version='^sed (GNU sed)'
      ;;
      stat)
        gnu_version='^stat (GNU coreutils)'
      ;;
    esac

    "$cmd" --version | sed 1q | grep -q -- "$gnu_version" || \
      { _perr "GNU version of command required -- '$cmd'"; err=1; }
    shift
  done

  [[ "$err" -gt 0 ]] && return 1
  return 0
}

_probe_dirs_files() {
  ## Probe for existence of needed directories and files

  # Printing error messages right away and only returning at the end because we
  # want to catch all missing files and directories at once.

  local f=  # init later?
  local err=0

  # Probe for top-level source directory
  if [[ ! -d "$SOURCE_DIR" ]]
  then
     _perr "directory not found -- '$SOURCE_DIR'"
     err=1
  else
    # Probe for sub-directories and files
    local d=
    for d in "$COMMON_DIR" "$PAGES_DIR"
    do
      if [[ ! -d "$d" ]]
      then
        _perr "directory not found -- '$d'"
        err=1
        continue
      fi
      case "$d" in
        "$COMMON_DIR")
          local sd=
          for sd in "$d/$CSS_DIR" "$d/$IMG_DIR"
          do
            if [[ ! -d "$sd" ]]
            then
              _perr "directory not found -- '$sd'"
              err=1
              continue
            fi
            case "$sd" in
              "$d/$CSS_DIR")
                f="$d/$CSS_DIR/$STYLESHEET"
                [[ -f "$f" ]] || { _perr "stylesheet not found -- '$f'"; err=1; }
              ;;
              "$d/$IMG_DIR")
                f="$d/$IMG_DIR/$FAVICON"
                [[ -f "$f" ]] || { _perr "favicon not found -- '$f'"; err=1; }

                f="$d/$IMG_DIR/$LOGO"
                [[ -f "$f" ]] || { _perr "logo not found -- '$f'"; err=1; }
              ;;
            esac
          done
          unset sd

          if [[ ! -f "$NAVTREE_FILE" ]]
          then
            _perr "navtree not found -- '$NAVTREE_FILE'"
            err=1 
          fi
        ;;
        "$PAGES_DIR")
          [[ -z "$(find -- "$PAGES_DIR" -mindepth 1 -maxdepth 1)" ]] && \
            { _perr "directory must not be empty -- '$PAGES_DIR'"; err=1; }
          if [[ ! -d "$PAGES_DIR_MASTER" ]]
          then
            _perr "master page directory not found -- '$PAGES_DIR_MASTER'"
            err=1
          else
            # Check whether master tree is basically sane
            for f in "$HEADER_PARAMS" "$FOOTER_PARAMS" "$HOME_PAGE"
            do
              f="$PAGES_DIR_MASTER/$f"
              [[ -f "$f" ]] || { _perr "file not found -- '$f'"; err=1; }
              # Improve error message.
            done
            # Determine if subtrees of all page directories are identical to
            # master subtree.
            mkdir -p -- "$TMP_DIR" || err=1
            find -- "$PAGES_DIR_MASTER" -printf '%P\n' | sort \
              > "$TMP_DIR"/treemaster
            local pd=
            for pd in $(_get_lang_dirs)
            do
              find -- "$PAGES_DIR/$pd" -printf '%P\n' | sort \
                > "$TMP_DIR"/treecopy
              cmp -s -- "$TMP_DIR"/treemaster "$TMP_DIR"/treecopy || { _perr \
                "'$PAGES_DIR/$pd': subtree not identical to '$PAGES_DIR_MASTER'";
                  err=1; }
                # Improve error message
            done
            unset pd
          fi
        ;;
      esac
    done
  fi

  mkdir -p -- "$OUTPUT_DIR" || err=1

  [[ "$err" -gt 0 ]] && return 1
  return 0
}

_env_checks() {
  ## Perform environment checks

  _command_available $TOOLS $GNU_TOOLS || return 1
  _command_is_gnu $GNU_TOOLS || return 1
  _probe_dirs_files || return 1
}

## Retrieval

_get_lang_dirs() {
  ## Find all language-specific page directories

  find -- "$PAGES_DIR" -maxdepth 1 -type d -not -name '.*' -name '??' | \
    sed 's/^.*\///'
  # `-not` is not POSIX, but widely supported.
}

_get_navtree() {
  ## Read all site navigation items into a global immutable array

  navtree=()

  local line=
  while read -r line
  do
    [[ "$line" =~ (^#)|(^[[:space:]]*$) ]] || navtree+=("$line")
  done < "$NAVTREE_FILE"
  unset line

  declare -r navtree
}

_get_pagetree() {
  ## Read page tree into a global immutable array

  pagetree=()

  local page_dir=
  while read -r page_dir
  do
    pagetree+=("${page_dir}/")
    # Append a slash so that the amount of slashes immediatley indicates the
    # sublevel of a page directory.
    # ($PAGES_DIR_MASTER).
  done < <(find -- "$PAGES_DIR_MASTER" -type d | sort | \
            sed -e "s,${PAGES_DIR_MASTER}/*,," -e '/^doc\/handbook/ d')
  unset page_dir

  declare -r pagetree
}

_get_sublevel() {

  local slashes="${1//[^\/]}"
  printf '%s' "${#slashes}"
}

_get_tree_flow() {
  ## [Add description]

  tree_flow=()

  local item=
  for item in "$@"
  do
    tree_flow+=("$(_get_sublevel "$item")")
  done
  unset item 

#  echo "${tree_flow[@]}"  ##TESTING
  declare -r tree_flow  # Why on earth does this prevent echo-ing the whole
                        # array later (like above)?!
}

## Display

_mk_tree_item_indent() {
  ## Compile the indentation string for a tree item

  local -r indent_base_cont='├─ '  # Find better name?
  local -r indent_base_end='└─ '   # Find better name?
  local -r indent_space='   '  # 3 spaces
  local -r indent_trunk='│  '

  local -r item="$1"
  local -r index_item="$2"
  local -r index_start="$(($index_item + 1))"
  local -r index_end="$((${#tree_flow[@]} - 1))"

  local indent=
  local indent_base=
  local next_lower_pos=
  local next_prev_pos=
  local next_same_pos=
  local prev=
  local sublevel=

  sublevel="${tree_flow[index_item]}"
  # Using tree_flow because it can represent pagetree as well as navtree.

  # Determine indent_base
  prev="$((sublevel - 1))"

  local i=
  for ((i="$index_start"; i<="$index_end"; ++i))
  do
    if [[ -z "$next_same_pos" ]] && [[ "${tree_flow[i]}" -eq "$sublevel" ]]
    then
      next_same_pos="$i"
    elif [[ -z "$next_prev_pos" ]] && [[ "${tree_flow[i]}" -eq "$prev" ]]
    then
      next_prev_pos="$i"
    fi
  done
  unset i

  if [[ -z "$next_same_pos" ]]
  then
    indent_base="$indent_base_end"
  elif [[ -n "$next_prev_pos" && "$next_prev_pos" -lt "$next_same_pos" ]]
  then
    indent_base="$indent_base_end"
  else
    indent_base="$indent_base_cont"
  fi

  indent="$indent_base"

  # Compile additonal indentation string for sublevels greater than 1 (prepend
  # to $indent_base)
  local s=
  for ((s="$sublevel"; s > 1; --s))
  do
    prev="$((s - 1))"
    next_prev_pos=
    next_lower_pos=

    # Determine whether and where the previous sublevel and any sublevel lower
    # than that occurs later in the tree.
    local i=
    for ((i="$index_start"; i<="$index_end"; ++i))
    do
      if [[ -z "$next_prev_pos" ]] && [[ "${tree_flow[i]}" -eq "$prev" ]]
      then
        next_prev_pos="$i"
      elif [[ -z "$next_lower_pos" ]] && [[ "${tree_flow[i]}" -lt "$prev" ]]
      then
        next_lower_pos="$i"
      fi
    done
    unset i

    if [[ -z "$next_prev_pos" ]]
    # No further parent occurs
    then
      indent="${indent_space}${indent}"
    elif [[ -n "$next_prev_pos" ]] && [[ -z "$next_lower_pos" ]]
    # Further parent occurs, but no further ancestor occurs
    then
      indent="${indent_trunk}${indent}"
    elif [[ -n "$next_prev_pos" ]] && [[ -n "$next_lower_pos" ]]
    # Further parent occurs and further ancestor occurs
    then
      # Next parent occurs before next ancestor:
      if [[ "$next_prev_pos" -lt "$next_lower_pos" ]]
      then
        indent="${indent_trunk}${indent}"
      # Next parent occurs after next ancestor: 
      elif [[ "$next_prev_pos" -gt "$next_lower_pos" ]]
      then
        indent="${indent_space}${indent}"
      fi
    fi
  done
  unset s

  printf '%s' "$indent"
}

_show_pagetree() {

  local -r index_start=0

  local indent=
  local item=
  local tree_mode=

  local -r index_end="$((${#pagetree[@]} - 1))"
  local -r index_width="${#index_end}"

  case "$1" in
    -add)
      tree_mode=add
    ;;
    -del)
      tree_mode=del
    ;;
    *)
      _perr "invalid option -- '$1'"
      return 1
    ;;
  esac

  _get_tree_flow "${pagetree[@]}"  # _mk_tree_item_indent needs this

  local i=
  for ((i="$index_start"; i<="$index_end"; ++i))
  do
    if [[ "$i" -eq 0 ]]
    then
      case "$tree_mode" in
        add)
          item='[new top-level page]'
        ;;
        del)
          continue
        ;;
      esac
    else
      item="${pagetree[i]%/}"  # Omit trailing slash before omitting
                               # everything up until the last slash below.
    fi

    indent="$(_mk_tree_item_indent "$item" "$i")"
    printf "  %${index_width}s  %s%s%s\n" "$i" "$indent" "${item##*/}"
  done
  unset i
}
